package com.vision.sales.sv.serv.rpc;

import java.util.ArrayList;
import java.util.List;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import com.sencha.gxt.data.shared.loader.PagingLoadResult;
import com.sencha.gxt.data.shared.loader.PagingLoadResultBean;
import com.vision.core.cm.data.Domain;
import com.vision.core.cm.db.data.Company;
import com.vision.core.cm.exception.RpcException;
import com.vision.core.cm.util.DateUtils;
import com.vision.core.sv.db.mgr.IDataManager;
import com.vision.core.sv.db.mgr.ISimpleCriteria;
import com.vision.core.sv.db.mgr.SimpleCriteria;
import com.vision.core.sv.util.CollectionUtils;
import com.vision.sales.cl.ui.component.TransactionLoadConfig;
import com.vision.sales.cm.data.CreditMemoType;
import com.vision.sales.cm.data.SalesPermissions;
import com.vision.sales.cm.data.TransactionStatus;
import com.vision.sales.cm.data.TransactionType;
import com.vision.sales.cm.db.data.CreditMemo;
import com.vision.sales.cm.db.data.SalesInvoice;
import com.vision.sales.cm.db.data.TransactionEntry;
import com.vision.sales.cm.serv.rpc.CreditMemoService;
import com.vision.sales.sv.util.TransactionHelper;

/**
 * @author Mark
 *
 */
@Service("credit_memo")
public class CreditMemoServiceImpl extends TransactionServiceImpl<CreditMemo> implements CreditMemoService {

	private static final long serialVersionUID = 1L;
	
	private static final Logger log = LoggerFactory.getLogger(CreditMemoServiceImpl.class);
	
	@Autowired
	@Qualifier("salesInvoiceManager")
	private IDataManager<SalesInvoice> invoiceManager;
	
	@Autowired
	@Qualifier("transactionEntryManager")
	private IDataManager<TransactionEntry> transactionEntryManager;
	
	public CreditMemoServiceImpl() {
		initGilead();
	}
	
	@Autowired
	@Qualifier("creditMemoManager")
	@Override
	public void setDataManager(IDataManager<CreditMemo> dataManager) {
		super.setDataManager(dataManager);
	}
	
	@Override
	protected Domain getDomain() {
		return Domain.CREDIT_MEMO;
	}
	
	// %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
	
	@Override
	public List<CreditMemo> getAll() throws RpcException {
		checkPermissions(SalesPermissions.ViewCreditMemo);
		
		return super.getAll();
	}

	@Transactional
	@Override
	public CreditMemo create(CreditMemo memo) throws RpcException {
		checkPermissions(SalesPermissions.CreateCreditMemo);
		checkHasCompany(memo.getCompany());
		
		Long memoInvoiceId = memo.getSalesInvoice() == null ? null : memo.getSalesInvoice().getId();
		// Fetch real sales invoice from database.
		SalesInvoice invoice = memoInvoiceId == null ? null : invoiceManager.getById(memoInvoiceId);
		if (invoice == null)
			throw new RpcException("The sales invoice does not exist.");
		
		if (!invoice.getCompany().equals(memo.getCompany()))
			throw new RpcException("Selected company and sales invoice company must match.");
		
		if (!invoice.getCustomer().equals(memo.getCustomer()))
			throw new RpcException("Selected customer and sales invoice customer must match.");
		
		if (DateUtils.isBefore(memo.getDate(), invoice.getDate()))
			throw new RpcException("Credit memo date must be after or same as sales invoice date.");
		
		// If the type is BAD_DEBTS, must have a selected sales invoice
		// and sales invoice.balance must be the same as the amount
		if (memo.getType() == CreditMemoType.BAD_DEBTS) {
			if (invoice.getBalance() != memo.getAmount())
				throw new RpcException("For bad debts, amount must be equal to the sales invoice balance.");
		}
		
		// CreditMemo amount must be less than or equal to sales invoice balance
		if (memo.getAmount() > invoice.getBalance())
			throw new RpcException("Amount must be less than or equal to the sales invoice balance.");

		// Set the status to pending
		memo.setStatus(TransactionStatus.PENDING);
		
		CreditMemo result = super.create(memo);
		
		// Update the sales invoice balance
		invoice.setBalance(invoice.getBalance() - result.getAmount());
		invoiceManager.update(invoice);
		
		logCreate(result);
		
		return result;
	}
	
	@Transactional
	@Override
	public CreditMemo update(CreditMemo memo) throws RpcException {
		checkPermissions(SalesPermissions.UpdateCreditMemo);
		
		CreditMemo existing = checkExisting(memo.getId());
		checkHasCompany(existing.getCompany());
		checkCanEditByStatus(existing, "credit memo");
		
		SalesInvoice invoice = invoiceManager.getById(existing.getSalesInvoice().getId());
		if (invoice == null)
			throw new RpcException("The sales invoice does not exist."); 
		
		// TODO 9 Check fields that can be modified
		// Status cannot be changed through this method, so we set it to the existing status
		memo.setStatus(existing.getStatus());
		
		CreditMemo result = super.update(memo);
		
		// Update the sales invoice balance
		// First, restore the invoice balance back to the amount before the memo was applied.
		// Then deduct the memo's new balance from the resulting invoice balance.
		invoice.setBalance(invoice.getBalance() + existing.getAmount());
		invoice.setBalance(invoice.getBalance() - result.getAmount());
		invoiceManager.update(invoice);
		
		logUpdate(existing, result);
		
		return result;
	}
	
	@Transactional
	@Override
	public CreditMemo delete(CreditMemo memo) throws RpcException {
		checkPermissions(SalesPermissions.DeleteCreditMemo);
		
		CreditMemo existing = checkExisting(memo.getId());
		checkHasCompany(existing.getCompany());
		checkCanDeleteByStatus(existing, "credit memo");
		
		SalesInvoice invoice = invoiceManager.getById(existing.getSalesInvoice().getId());
		if (invoice == null)
			throw new RpcException("The sales invoice does not exist.");
		
		CreditMemo result = super.delete(existing);
		
		// Update the sales invoice balance
		invoice.setBalance(invoice.getBalance() + existing.getAmount());
		invoiceManager.update(invoice);
		
		logDelete(result);
		
		return result;
	}
	
	@SuppressWarnings("unchecked")
	@Override
	public PagingLoadResult<CreditMemo> getList(TransactionLoadConfig config) throws RpcException {
		checkPermissions(SalesPermissions.ViewCreditMemo);
		
		config.setCompanies(new ArrayList<Company>(getLoggedInUser().getCompanies()));

		ISimpleCriteria crit = TransactionHelper.createCriteria(CreditMemo.class, config);
		List<CreditMemo> list = (List<CreditMemo>) TransactionHelper.getList(crit, config, getTemplate());
		
		ISimpleCriteria countCrit = TransactionHelper.createCountCriteria(CreditMemo.class, config);
		Long count = (Long) getTemplate().findByCriteria(countCrit.getDetached()).get(0);
		
		PagingLoadResultBean<CreditMemo> result = new PagingLoadResultBean<CreditMemo>(list, count.intValue(), config.getOffset());
		return result;
	}
	
	@Override
	public List<CreditMemo> getCreditMemos(TransactionStatus status) throws RpcException {
		ISimpleCriteria crit = SimpleCriteria.forClass(CreditMemo.class);
		crit.eq("status", status);
		List<CreditMemo> list = findByCriteria(crit);
		return list;
	}
	
	@Transactional
	@Override
	public List<CreditMemo> postCreditMemos(List<CreditMemo> creditMemos) throws RpcException {
		checkPermissions(SalesPermissions.PostCreditMemo);
		
		List<Long> ids = CollectionUtils.listFrom(creditMemos, "getId");
		List<CreditMemo> existingMemos = getDataManager().getByIds(CreditMemo.COL_ID, ids);
		if (existingMemos.isEmpty())
			return existingMemos;
		
		List<CreditMemo> results = new ArrayList<CreditMemo>(existingMemos.size());
		
		for (CreditMemo existing : existingMemos) {
			checkHasCompany(existing.getCompany());
			
			if (existing.getStatus() == TransactionStatus.POSTED) {
				log.debug("Credit Memo '{}' has been posted. This will be skipped.", existing.getCreditMemoNo());
				continue;
			}

			log.debug("Posting credit memo {}...", existing.getCreditMemoNo());
			existing.setStatus(TransactionStatus.POSTED);
			CreditMemo result = getDataManager().update(existing);

			log.debug("Creating transaction entry for credit memo {}...", existing.getCreditMemoNo());
			TransactionEntry te = createTransactionEntry(existing);			
			transactionEntryManager.create(te);
			
			// Log the update
			logUpdate(existing, result);
			
			results.add(result);
		}
		
		return results;
	}
	
	// Static Helpers %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
	
	private static TransactionEntry createTransactionEntry(CreditMemo memo) {
		TransactionEntry te = new TransactionEntry();
		te.setCompany(memo.getCompany());
		te.setDate(memo.getDate());
		te.setTransactionId(memo.getId());
		te.setTransactionRefNo(memo.getCreditMemoNo());
		te.setCustomer(memo.getCustomer());
		te.setSalesman(memo.getCustomer().getSalesman()); // TODO Get directly from memo, add salesman field
		te.setProduct(memo.getSalesInvoice().getProduct()); // TODO Get directly from memo, add product field
		
		if (memo.getType() == CreditMemoType.DISCOUNTS)
			te.setType(TransactionType.CREDIT_MEMO_DISCOUNTS);
		else if (memo.getType() == CreditMemoType.BAD_DEBTS)
			te.setType(TransactionType.CREDIT_MEMO_BAD_DEBTS);
		else if (memo.getType() == CreditMemoType.REJECTS)
			te.setType(TransactionType.CREDIT_MEMO_REJECTS);
		else if (memo.getType() == CreditMemoType.RETURNS)
			te.setType(TransactionType.CREDIT_MEMO_RETURNS);
		
		if (te.getType().isDebit()) {
			te.setDebit(memo.getAmount());
			te.setCredit(0d);
		} else {
			te.setDebit(0d);
			te.setCredit(memo.getAmount());
		}
		
		return te;
	}

}
